<!DOCTYPE html>
<html lang="zh-Hans">

<head>
  <title>ms-ra-forwarder</title>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link href="./bootstrap/dist/css/bootstrap.min.css" rel="stylesheet" />
</head>

<body>
  <nav class="navbar navbar-light bg-light">
    <div class="container-fluid">
      <a class="navbar-brand" target="_blank" href="https://github.com/rogueme/ms-ra-forwarder">ms-ra-forwarder</a>
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="github-button" href="https://github.com/rogueme/ms-ra-forwarder" data-size="large"
            data-show-count="true" aria-label="Star rogueme/ms-ra-forwarder on GitHub">Star</a>
        </li>
      </ul>
    </div>
  </nav>
  <div class="container" style="margin-top: 30px;">
    <div class="row">
      <div class="col">
        <div class="alert alert-primary" role="alert">
          阅读更新<a href="https://wwi.lanzoup.com/b0c78oosj">兰奏盘不定时更新</a> 密码：1mqp<br>
          点击这里可以领支付宝红包了<a href="./hongbao.html">点我查看</a>！
        </div>
      </div>
    </div>
    <form>
      <div class="mb-3">
        <label for="name" class="form-label">配置名称：</label>
        <input name="name" type="text" class="form-control" value="大声朗读" />
        <div class="form-text">在阅读中显示的名称。默认为“大声朗读”。</div>
      </div>
      <div class="mb-3">
        <label for="voiceName" class="form-label">声音：</label>
        <select name="voiceName" class="form-select"  onchange="selectOnchang(this)">
        </select>
        <div class="form-text">角色名称-性别-地区-风格数量<br/>33种支持中文发音的角色，风格列表仅列出该角色发音人支持的风格，更新于2022/07/19！推荐使用晓晓或者云扬/云希（晓晓yyds）。</div>
      </div>
      <div class="mb-3">
        <label for="styleName" class="form-label">讲话风格：</label>
        <div class="input-group">
          <span class="input-group-text">名称</span>
          <select name="styleName" class="form-select" disabled>
          </select>
        </div>
        <div class="input-group">
          <span class="input-group-text">风格强度</span>
          <input name="styleDegree" class="form-control" type="number" min="0.01" max="2.00" step=".01" value="1.00" disabled>
          <span class="input-group-text">讲话语速</span>
          <input name="speakSpeed" class="form-control" type="number" min="0" max="100" step="1" value="25">
          <span class="input-group-text">%</span>
        </div>
        <div class="form-text">
          每个声音支持的讲话风格都不一样，具体可以参考：https://docs.microsoft.com/zh-CN/azure/cognitive-services/speech-service/speech-synthesis-markup?tabs=csharp#adjust-speaking-styles
        </div>
      </div>

      <div class="mb-3">
        <label for="voiceFormat" class="form-label">音频格式：</label>
        <div class="input-group">
          <select name="voiceFormat" class="form-select">
            <option class="btn-danger" value="raw-16khz-16bit-mono-pcm">raw-16khz-16bit-mono-pcm</option>
            <option class="btn-danger" value="raw-48khz-16bit-mono-pcm">raw-48khz-16bit-mono-pcm</option>
            <option class="btn-danger" value="raw-8khz-8bit-mono-mulaw">raw-8khz-8bit-mono-mulaw</option>
            <option class="btn-danger" value="raw-8khz-8bit-mono-alaw">raw-8khz-8bit-mono-alaw</option>
            <option class="btn-danger" value="raw-16khz-16bit-mono-truesilk">raw-16khz-16bit-mono-truesilk</option>
            <option class="btn-danger" value="raw-24khz-16bit-mono-truesilk">raw-24khz-16bit-mono-truesilk</option>
            <option class="btn-danger" value="riff-16khz-16bit-mono-pcm">riff-16khz-16bit-mono-pcm</option>
            <option class="btn-danger" value="riff-24khz-16bit-mono-pcm">riff-24khz-16bit-mono-pcm</option>
            <option class="btn-danger" value="riff-48khz-16bit-mono-pcm">riff-48khz-16bit-mono-pcm</option>
            <option class="btn-danger" value="riff-8khz-8bit-mono-mulaw">riff-8khz-8bit-mono-mulaw</option>
            <option class="btn-danger" value="riff-8khz-8bit-mono-alaw">riff-8khz-8bit-mono-alaw</option>
            <option class="btn-danger" value="audio-16khz-32kbitrate-mono-mp3">audio-16khz-32kbitrate-mono-mp3</option>
            <option class="btn-danger" value="audio-16khz-64kbitrate-mono-mp3">audio-16khz-64kbitrate-mono-mp3</option>
            <option class="btn-danger" value="audio-16khz-128kbitrate-mono-mp3">audio-16khz-128kbitrate-mono-mp3</option>
            <option value="audio-24khz-48kbitrate-mono-mp3">audio-24khz-48kbitrate-mono-mp3</option>
            <option value="audio-24khz-96kbitrate-mono-mp3">audio-24khz-96kbitrate-mono-mp3</option>
            <option value="audio-24khz-160kbitrate-mono-mp3">audio-24khz-160kbitrate-mono-mp3</option>
            <option class="btn-danger" value="audio-48khz-96kbitrate-mono-mp3">audio-48khz-96kbitrate-mono-mp3</option>
            <option class="btn-danger" value="audio-48khz-192kbitrate-mono-mp3">audio-48khz-192kbitrate-mono-mp3</option>
            <option class="btn-danger" value="webm-16khz-16bit-mono-opus">webm-16khz-16bit-mono-opus</option>
            <option value="webm-24khz-16bit-mono-opus" selected>webm-24khz-16bit-mono-opus</option>
            <option class="btn-danger" value="ogg-16khz-16bit-mono-opus">ogg-16khz-16bit-mono-opus</option>
            <option class="btn-danger" value="ogg-24khz-16bit-mono-opus">ogg-24khz-16bit-mono-opus</option>
            <option class="btn-danger" value="ogg-48khz-16bit-mono-opus">ogg-48khz-16bit-mono-opus</option>
          </select>
          <button type="button" class="btn btn-success" onclick='createjson()'>添加引擎</button>
        </div>
        <div class="form-text">可以调整音频的格式和质量。质量越高，生成的文件越大，对网速和流量的需求越高。请根据自己的情况选择。（我禁用了一些在阅读中播放不了的格式）</div>
      </div>

      <div class="mb-3">
        <label for="token" class="form-label">凭据（TOKEN）：</label>
        <input name="token" class="form-control" type="text" value="">
        <div class="form-text">如果没有设置 TOKEN 环境变量请留空。</div>
      </div>

      <div class="mb-3">
        <label for="previewText" class="form-label">测试文本：</label>
        <textarea name="previewText" class="form-control" rows="3" cols="">Hi,我是微软的智能语音助手,这里是中文语音示例,如果喜欢的话请点个Star吧!</textarea>
      </div>

      <div class="col-auto">
        <button id="previewButton" type="button" class="btn btn-secondary" onclick="preview()">测试</button>
        <button type="button" class="btn btn-info" onclick='checkLegadoUrl()'>查看引擎参数</button>
        <button type="button" class="btn btn-primary" onclick='createLegadoUrl()'>生成引擎链接</button>
      </div>
    </form>
  </div>


  <div id="createlegadoUrlModal" class="modal " tabindex="-1">
    <div class="modal-dialog modal-lg">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">阅读链接</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          <div id="legadoUrlQRCode" style="text-align:center">
          </div>
          <div style="text-align:center">
              <a id="legadoUrlA" href>
                <button id="legadoUrlB" type="button" class="btn btn-warning">导入
                </button>
              </a>
            </div>
          <textarea id="legadoUrl" class="form-control" readonly rows="3" cols="2" value></textarea>
          <div>
          </div>
          <div class="alert alert-primary" role="alert">
            请在阅读的朗读引擎设置中选择网络导入此链接。
          </div>
        </div>
      </div>
    </div>
  </div>
    <div id="checklegadoUrlModal" class="modal " tabindex="-1">
    <div class="modal-dialog modal-lg">
      <div class="modal-content">
        <div class="modal-header">
          <h5 class="modal-title">引擎参数</h5>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body table-responsive">
          <table class="table" id="ttstb">
            <thead>
              <tr>
                <th>序号</th>
                <th>角色名称</th>
                <th>语速</th>
                <th>音频格式</th>
              </tr>
            </thead>
          </table>
          <div class="col-auto">
           <button type="button" class="btn btn-danger" data-bs-dismiss="modal" onclick="delallttsdata()">清空引擎</button>
         </div>
        </div>
      </div>
    </div>
  </div>
  <script async defer src="https://buttons.github.io/buttons.js"></script>
  <script type="text/javascript" src="TTSedge.json"></script>
  <script src="./base64/base64.js"></script>
  <script src="./bootstrap/dist/js/bootstrap.bundle.min.js"></script>
  <script src="./qrcode-svg/lib/qrcode.min.js"></script>
  <script>
    var voices = [];
    var alljson = [];
    var base64arr = {};
    //fetch('https://speech.platform.bing.com/consumer/speech/synthesize/readaloud/voices/list?trustedclienttoken=6A5AA1D4EAFF4E9FB37E23D68491D6F4')
    getdata();
    refreshVoice();
    function selectOnchang(obj){ 
    //获取被选中的option标签选项 
    getstyleName(obj.selectedIndex);
    }
    function getdata() {
      let itemIndex = 0;
      data.forEach(item => {
        if (item['LocaleName'].indexOf('Chinese') >= 0) {
          voices[itemIndex] = item;
          itemIndex += 1;
        }
        else if (Array.isArray(item['SecondaryLocaleList'])) {
          item['SecondaryLocaleList'].forEach(localitem => {
            if (localitem == "zh-CN" || localitem == "zh-HK" || localitem == "zh-TW") {
              voices[itemIndex] = item;
              itemIndex += 1;
              return;
            }
          })
        }
      })
    }
    function refreshVoice() {
      let voice = document.getElementsByName('voiceName')[0];
      voices.forEach(item => {
          let option = document.createElement('option');
          let Status = "";
          if (item['Status'] =="Preview") {
            Status = "[预览]";
          }
          option.value = item['ShortName'];
          let Gender = "男";
          if (item['Gender'] == "Female"){
            Gender = "女";
          }
          let Locale = "中国大陆";
          if (item['Locale'] == "zh-HK"){
            Locale = "中国香港";
          } else if (item['Locale'] == "zh-TW"){
            Locale = "中国台湾";
          } else if (item['Locale'] == "en-US"){
            Locale = "美国";
          }
          let count = 1;
          if (Array.isArray(item['StyleList'])){
            count = item['StyleList'].length + 1;
          }
          option.innerText = item['LocalName'] + Status + "-" + Gender + "-" + Locale + "-" + count + "个";
          voice.append(option);
      });
      getstyleName(0);
    }
  function getstyleName(styleIndex){
    let style = document.getElementsByName('styleName')[0];
    style.options.length=0;
    let option = document.createElement('option');
    let Status = "";
    if (voices[styleIndex].Status =="Preview") {
      Status = "[预览]";
    }
    option.value = "normal";
    option.innerText = "normal[默认]";
    style.append(option);
    document.getElementsByName('name')[0].value = '微软' + voices[styleIndex].LocalName + Status;
    let styles = voices[styleIndex].StyleList;
    if (Array.isArray(styles)){
      styles.forEach(styleName =>{
        let option2 = document.createElement('option');
        option2.value = styleName;
        let opvalue = styledata[styleName.toLowerCase()];
        if (opvalue == undefined){
          opvalue = "未知";
          }
        option2.innerText = styleName + "[" + opvalue + "]";
        style.append(option2);
    });
    }

    
  }
    function createSSML(text, voiceName, speakSpeed) {
      var ssml = '\
        <speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">\
          <voice name="'+ voiceName + '">\
              <prosody rate="' + speakSpeed + '%" pitch="0Hz">\
                  '+ text + '\
             </prosody >\
          </voice >\
        </speak > '

      return ssml;
    }

    function preview() {
      var headers = {'Content-Type': 'text/plain'};
      var voiceName = document.getElementsByName('voiceName')[0].value;
      var voiceFormat = document.getElementsByName('voiceFormat')[0].value;
      //var styleName = document.getElementsByName('styleName')[0].value;
      //var styleDegree = document.getElementsByName('styleDegree')[0].value;
      var token = document.getElementsByName('token')[0].value;
      var previewText = document.getElementsByName('previewText')[0].value;
      var speakSpeed = document.getElementsByName('speakSpeed')[0].value;
      var ssml = createSSML(previewText, voiceName, speakSpeed)
      if (token) {
        headers['Authorization'] = 'Bearer ' + token;
      }

      headers['Format'] = voiceFormat;

      var button = document.getElementById('previewButton');
      button.disabled = true;
      var ctx = new AudioContext();
      fetch('/api/ra', {
        method: 'post',
        headers: headers,
        body: ssml
      }).then(response => {
        if (response.status == 200) {
          return response.arrayBuffer()
        } else if (response.status == 401) {
          throw '无效的密钥';
        } else {
          return response.text().then(text => Promise.reject(text));
        }
      })
      .then(arrayBuffer => ctx.decodeAudioData(arrayBuffer))
      .then(audio => {
        var player = ctx.createBufferSource();
        player.buffer = audio;
        player.connect(ctx.destination);
        player.start(ctx.currentTime);
      })
      .catch(reason => {
        alert(reason);
      })
      .finally(()=>{
        button.disabled = false;
      });
    }

    function delallttsdata(){
      alljson.length = 0;
    }
    
    function createLegadoUrl() {
      var url = window.location.protocol + '//' + window.location.host;
      var token = document.getElementsByName('token')[0].value;
      var alljson2 = alljson;
      if (alljson.length == 0){
        createjson();
      }
      base64arr = {
        'ttsdata':alljson,
        'token':token,
        'type': "ra",
        'url':encodeURI(url),
      };
      var allurl = url + '/api/legado?data=' + encodeURIComponent(Base64.encode(JSON.stringify(base64arr)));
      var svg = new QRCode(allurl).svg();
      var modal = new bootstrap.Modal(document.getElementById('createlegadoUrlModal'));
      modal.show();
      document.getElementById('legadoUrlQRCode').innerHTML = svg;
      document.getElementById('legadoUrl').value = allurl;
      document.getElementById('legadoUrlB').innerHTML = '一键导入' + alljson.length + '条语音到阅读'
      
      document.getElementById('legadoUrlA').href = "legado://import/httpTTS?src=" + allurl;
      ceshi();
      alljson = alljson2;
    }
    function createjson() {
      var name = document.getElementsByName('name')[0].value;
      var voiceName = document.getElementsByName('voiceName')[0].value;
      var voiceFormat = document.getElementsByName('voiceFormat')[0].value;
      //var styleName = document.getElementsByName('styleName')[0].value;
      //var styleDegree = document.getElementsByName('styleDegree')[0].value;
      var speakSpeed = document.getElementsByName('speakSpeed')[0].value;
      var ttsarr = {
        'name':encodeURI(name),
        'voiceName':voiceName,
        'voiceFormat':voiceFormat,
        //'styleName':styleName,
        //'styleDegree':styleDegree,
        'speakSpeed':speakSpeed
      };
      alljson.push(ttsarr);
    }
    function gettabletd(text){
      let lTd = document.createElement("td");
      let lText = document.createTextNode(text);
      lTd.appendChild(lText);
      return lTd;
    }
    function checkLegadoUrl() {
      let jsonIndex = 0;
      let table = document.getElementById("ttstb");
      let tbody = table.getElementsByTagName("tbody");
      if (tbody.length > 0){
        table.removeChild(tbody[0]);
      }
      tbody = document.createElement('tbody');
      table.appendChild(tbody);
      alljson.forEach(item =>{
        let tr = document.createElement("tr");
        let idTd = gettabletd(jsonIndex + 1);
        let nameTd = gettabletd(decodeURIComponent(item['name']));
        let voiceNameTd = gettabletd(item['voiceName']);
        let voiceFormatTd = gettabletd(item['voiceFormat']);
        //let styleNameTd = gettabletd(item['styleName']);
        let speakSpeed = gettabletd(item['speakSpeed'] + '%');
        //let styleDegreeTd = gettabletd(item['styleDegree']);
        tr.appendChild(idTd);
        tr.appendChild(voiceNameTd);
        //tr.appendChild(styleNameTd);
        //tr.appendChild(styleDegreeTd);
        tr.appendChild(speakSpeed);
        tr.appendChild(voiceFormatTd);
        tbody.appendChild(tr);
        jsonIndex += 1;
      })
      var modal = new bootstrap.Modal(document.getElementById('checklegadoUrlModal'));
      modal.show();
    }
  </script>
</body>

</html>
